﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;

namespace EffiProz.Core
{
    public class ExpressionColumn : Expression
    {
        public static ExpressionColumn[] emptyArray =
         new ExpressionColumn[] { };

        //
        public ColumnSchema column;
        public String schema;
        public String tableName;
        public String columnName;
        public String columnNameOriginal = String.Empty;
        public RangeVariable rangeVariable;

        //
        public NumberSequence sequence;
        public bool isWritable;    // = false; true if column of writable table

        /**
         * Creates a OpCodes.COLUMN expression
         */
        public ExpressionColumn(String schema, String table, String column)
            : base(OpTypes.COLUMN)
        {

            this.schema = schema;
            tableName = table;
            columnName = column;
        }

        public ExpressionColumn(ColumnSchema column)
            : base(OpTypes.COLUMN)
        {

            columnName = column.getName().name;
            columnNameOriginal = column.getName().originalName;
        }

        public ExpressionColumn(RangeVariable rangeVar, ColumnSchema column) :
            base(OpTypes.COLUMN)
        {

            columnIndex = rangeVar.rangeTable.findColumn(column.getName().name);

            setAttributesAsColumn(rangeVar, columnIndex);
        }

        public ExpressionColumn(RangeVariable rangeVar, ColumnSchema column, int index)
            : base(OpTypes.COLUMN)
        {


            columnIndex = index;

            setAttributesAsColumn(rangeVar, columnIndex);
        }

        /**
         * Creates a temporary OpCodes.COLUMN expression
         */
        public ExpressionColumn(Expression e, int colIndex, int rangePosition)
            : base(OpTypes.SIMPLE_COLUMN)
        {

            dataType = e.dataType;
            columnIndex = colIndex;
            alias = e.alias;
            this.rangePosition = rangePosition;
        }

        public ExpressionColumn()
            : base(OpTypes.ASTERISK)
        {

        }

        public ExpressionColumn(int type)
            : base(type)
        {

            if (type == OpTypes.DYNAMIC_PARAM)
            {
                _isParam = true;
            }
        }

        public ExpressionColumn(int type, string paramName)
            : base(type)
        {
            if (type == OpTypes.NAMED_PARAM)
            {
                _isParam = true;
                columnName = paramName;
            }
        }

        public ExpressionColumn(Expression[] nodes, String name) :
            base(OpTypes.COALESCE)
        {

            this.nodes = nodes;
            this.columnName = name;
        }

        /**
         * Creates an OpCodes.ASTERISK expression
         */
        public ExpressionColumn(String schema, String table)
            : base(OpTypes.MULTICOLUMN)
        {

            this.schema = schema;
            tableName = table;
        }

        /**
         * Creates a OpCodes.SEQUENCE expression
         */
        public ExpressionColumn(NumberSequence sequence)
            : base(OpTypes.SEQUENCE)
        {

            this.sequence = sequence;
            dataType = sequence.getDataType();
        }

        public void setAttributesAsColumn(RangeVariable range, int i)
        {

            if (range.variables != null)
            {
                columnIndex = i;
                column = range.getColumn(i);
                dataType = column.getDataType();
                rangeVariable = range;
            }
            else
            {
                Table t = range.getTable();

                columnIndex = i;
                column = range.getColumn(i);
                dataType = column.getDataType();
                columnName = column.getName().name;
                columnNameOriginal = column.getName().name;
                tableName = t.getName().name;
                schema = t.getSchemaName().name;

                if (alias == null && range.hasColumnAlias())
                {
                    alias = range.getColumnAliasName(i);
                }

                rangeVariable = range;

                rangeVariable.addColumn(columnIndex);
            }
        }

        public override void setAttributesAsColumn(ColumnSchema column, bool isWritable)
        {

            this.column = column;
            dataType = column.getDataType();
            this.isWritable = isWritable;
        }

        public override QNameManager.SimpleName getSimpleName()
        {

            if (alias != null)
            {
                return alias;
            }

            if (column != null)
            {
                return column.getName();
            }

            if (opType == OpTypes.COALESCE)
            {
                return nodes[LEFT].getSimpleName();
            }

            return null;
        }

        public override String getAlias()
        {

            if (alias != null)
            {
                return alias.name;
            }

            if (opType == OpTypes.COLUMN)
            {
                return columnName;
            }

            if (opType == OpTypes.COALESCE)
            {
                return columnName;
            }

            return "";
        }

        public override String getOriginalAlias()
        {

            if (alias != null)
            {
                return alias.originalName;
            }

            if (opType == OpTypes.COLUMN)
            {
                return String.IsNullOrEmpty(columnNameOriginal) ? columnName : columnNameOriginal;
            }

            if (opType == OpTypes.COALESCE)
            {
                return String.IsNullOrEmpty(columnNameOriginal) ? columnName : columnNameOriginal;
            }

            return "";
        }

        public String getBaseColumnName()
        {

            if (opType == OpTypes.COLUMN && rangeVariable != null)
            {
                return rangeVariable.getTable().getColumn(
                    columnIndex).getName().name;
            }

            return null;
        }

        public QNameManager.QName getBaseColumnHsqlName()
        {
            return column.getName();
        }

        public override  void collectObjectNames(Set set)
        {


            switch (opType)
            {

                case OpTypes.SEQUENCE:
                    QNameManager.QName name = ((NumberSequence)valueData).getName();

                    set.add(name);

                    return;
                case OpTypes.NAMED_PARAM:
                case OpTypes.MULTICOLUMN:
                case OpTypes.DYNAMIC_PARAM:
                case OpTypes.ASTERISK:
                case OpTypes.SIMPLE_COLUMN:
                case OpTypes.COALESCE:
                    break;
                                    
                case OpTypes.PARAMETER:
                case OpTypes.VARIABLE:
                case OpTypes.COLUMN:
                    set.add(column.getName());
                    if (column.getName().parent != null)
                    {
                        set.add(column.getName().parent);
                    }
                    return;
            }
        }

        public override  String getColumnName()
        {

            if (opType == OpTypes.COLUMN && column != null)
            {
                return column.getName().name;
            }

            return getAlias();
        }

        public override ColumnSchema getColumn()
        {
            return column;
        }

        public String getSchemaName()
        {
            return schema;
        }

        public override RangeVariable getRangeVariable()
        {
            return rangeVariable;
        }

        public override EfzList resolveColumnReferences(RangeVariable[] rangeVarArray,
                int rangeCount, EfzList unresolvedSet, bool acceptsSequences)
        {

            switch (opType)
            {

                case OpTypes.SEQUENCE:
                    if (!acceptsSequences)
                    {
                        throw Error.error(ErrorCode.X_42598);
                    }
                    break;

                case OpTypes.MULTICOLUMN:
                case OpTypes.NAMED_PARAM:
                case OpTypes.DYNAMIC_PARAM:
                case OpTypes.ASTERISK:
                case OpTypes.SIMPLE_COLUMN:
                case OpTypes.COALESCE:
                    break;

                case OpTypes.PARAMETER:
                case OpTypes.VARIABLE:
                case OpTypes.COLUMN:
                    if (rangeVariable != null)
                    {
                        return unresolvedSet;
                    }

                    for (int i = 0; i < rangeCount; i++)
                    {
                        RangeVariable rangeVar = rangeVarArray[i];

                        if (rangeVar == null)
                        {
                            continue;
                        }

                        if (resolveColumnReference(rangeVar))
                        {
                            return unresolvedSet;
                        }
                    }

                    if (unresolvedSet == null)
                    {
                        unresolvedSet = new ArrayListIdentity();
                    }

                    unresolvedSet.add(this);

                    break;
            }

            return unresolvedSet;
        }

        public bool resolveColumnReference(RangeVariable rangeVar)
        {

            if (tableName == null)
            {
                Expression e = rangeVar.getColumnExpression(columnName);

                if (e != null)
                {
                    opType = e.opType;
                    nodes = e.nodes;
                    dataType = e.dataType;

                    return true;
                }

                if (rangeVar.variables != null)
                {
                    int colIndex = rangeVar.findColumn(columnName);

                    if (colIndex == -1)
                    {
                        return false;
                    }

                    ColumnSchema column = rangeVar.getColumn(colIndex);

                    if (column.getParameterMode()
                            == SchemaObjectParameterModes.PARAM_OUT)
                    {
                        return false;
                    }
                    else
                    {
                        opType = rangeVar.isVariable ? OpTypes.VARIABLE
                                                     : OpTypes.PARAMETER;

                        setAttributesAsColumn(rangeVar, colIndex);

                        return true;
                    }
                }
            }

            if (!rangeVar.resolvesTableName(this))
            {
                return false;
            }

            int _colIndex = rangeVar.findColumn(columnName);

            if (_colIndex != -1)
            {
                setAttributesAsColumn(rangeVar, _colIndex);

                return true;
            }

            return false;
        }

        public override  void resolveTypes(Session session, Expression parent)
        {

            switch (opType)
            {

                case OpTypes.DEFAULT:
                    if (parent != null && parent.opType != OpTypes.ROW)
                    {
                        throw Error.error(ErrorCode.X_42544);
                    }
                    break;

                case OpTypes.COALESCE:
                    {
                        SqlType type = null;

                        for (int i = 0; i < nodes.Length; i++)
                        {
                            type = SqlType.getAggregateType(nodes[i].dataType, type);
                        }

                        dataType = type;

                        break;
                    }
            }
        }

        public override Object getValue(Session session)
        {

            switch (opType)
            {

                case OpTypes.DEFAULT:
                    return null;

                case OpTypes.VARIABLE:
                    {
                        return session.sessionContext.routineVariables[columnIndex];
                    }
                case OpTypes.PARAMETER:
                    {
                        return session.sessionContext.routineArguments[columnIndex];
                    }
                case OpTypes.COLUMN:
                    {
                        Object[] data =
                            (Object[])session.sessionContext
                                .rangeIterators[rangeVariable.rangePosition]
                                .getCurrent();
                        Object value = data[columnIndex];
                        SqlType colType = column.getDataType();

                        if (!dataType.Equals(colType))
                        {
                            value = dataType.convertToType(session, value, colType);
                        }

                        return value;
                    }
                case OpTypes.SIMPLE_COLUMN:
                    {
                        Object[] data =
                            (Object[])session.sessionContext
                                .rangeIterators[rangePosition].getCurrent();

                        return data[columnIndex];
                    }
                case OpTypes.COALESCE:
                    {
                        Object value = null;

                        for (int i = 0; i < nodes.Length; i++)
                        {
                            value = nodes[i].getValue(session, dataType);

                            if (value != null)
                            {
                                return value;
                            }
                        }

                        return value;
                    }
                case OpTypes.NAMED_PARAM:
                case OpTypes.DYNAMIC_PARAM:
                    {
                        return session.sessionContext.dynamicArguments[parameterIndex];
                    }
                case OpTypes.SEQUENCE:
                    {
                        return session.sessionData.getSequenceValue(sequence);
                    }
                case OpTypes.ASTERISK:
                case OpTypes.MULTICOLUMN:
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "ExpressionColumn");
            }
        }

        public override  String getSQL()
        {

            switch (opType)
            {

                case OpTypes.DEFAULT:
                    return Tokens.T_DEFAULT;

                case OpTypes.DYNAMIC_PARAM:
                    return Tokens.T_QUESTION;

                case OpTypes.NAMED_PARAM:
                    return Tokens.T_ATSYM + columnName;

                case OpTypes.ASTERISK:
                    return "*";

                case OpTypes.COALESCE:

                    //                return columnName;
                    return alias.getStatementName();

                case OpTypes.VARIABLE:
                case OpTypes.PARAMETER:
                case OpTypes.COLUMN:
                    {
                        if (column == null)
                        {
                            if (alias != null)
                            {
                                return alias.getStatementName();
                            }
                            else
                            {
                                return columnName;
                            }
                        }

                        if (rangeVariable.tableAlias == null)
                        {
                            return column.getName().getSchemaQualifiedStatementName();
                        }
                        else
                        {
                            StringBuilder sb = new StringBuilder();

                            sb.Append(rangeVariable.tableAlias.getStatementName());
                            sb.Append('.');
                            sb.Append(column.getName().statementName);

                            return sb.ToString();
                        }
                    }
                case OpTypes.MULTICOLUMN:
                    {
                        if (nodes.Length == 0)
                        {
                            return "*";
                        }

                        StringBuilder sb = new StringBuilder();

                        for (int i = 0; i < nodes.Length; i++)
                        {
                            Expression e = nodes[i];

                            if (i > 0)
                            {
                                sb.Append(',');
                            }

                            String s = e.getSQL();

                            sb.Append(s);
                        }

                        return sb.ToString();
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "ExpressionColumn");
            }
        }

        public override  String describe(Session session, int blanks)
        {

            StringBuilder sb = new StringBuilder(64);

            sb.Append('\n');

            for (int i = 0; i < blanks; i++)
            {
                sb.Append(' ');
            }

            switch (opType)
            {

                case OpTypes.DEFAULT:
                    sb.Append(Tokens.T_DEFAULT);
                    break;

                case OpTypes.ASTERISK:
                    sb.Append("OpTypes.ASTERISK ");
                    break;

                case OpTypes.VARIABLE:
                    sb.Append("VARIABLE: ");
                    sb.Append(column.getName().name);
                    break;

                case OpTypes.PARAMETER:
                    sb.Append(Tokens.T_PARAMETER).Append(": ");
                    sb.Append(column.getName().name);
                    break;

                case OpTypes.COALESCE:
                    sb.Append(Tokens.T_COLUMN).Append(": ");
                    sb.Append(columnName);

                    if (alias != null)
                    {
                        sb.Append(" AS ").Append(alias.name);
                    }
                    break;

                case OpTypes.COLUMN:
                    sb.Append(Tokens.T_COLUMN).Append(": ");
                    sb.Append(column.getName().name);

                    if (alias != null)
                    {
                        sb.Append(" AS ").Append(alias.name);
                    }

                    sb.Append(' ').Append(Tokens.T_TABLE).Append(": ").Append(
                        tableName);
                    break;

                case OpTypes.DYNAMIC_PARAM:
                    sb.Append("DYNAMIC PARAM: ");              
                    sb.Append(", TYPE = ").Append(dataType.getNameString());
                    break;

                case OpTypes.NAMED_PARAM:
                    sb.Append("NAMED PARAM: ");
                    sb.Append(columnName);
                    sb.Append(", TYPE = ").Append(dataType.getNameString());
                    break;

                case OpTypes.SEQUENCE:
                    sb.Append(Tokens.T_SEQUENCE).Append(": ");
                    sb.Append(sequence.getName().name);
                    break;

                case OpTypes.MULTICOLUMN:
                    break;
                // shouldn't get here
            }

            return sb.ToString();
        }

        /**
         * Returns the table name for a column expression as a string
         *
         * @return table name
         */
        public String getTableName()
        {

            if (opType == OpTypes.MULTICOLUMN)
            {
                return tableName;
            }

            if (opType == OpTypes.COLUMN)
            {
                if (rangeVariable == null)
                {
                    return tableName;
                }
                else
                {
                    return rangeVariable.getTable().getName().name;
                }
            }

            return "";
        }

        public static void checkColumnsResolved(EfzList set)
        {

            if (set != null && !set.isEmpty())
            {
                ExpressionColumn e = (ExpressionColumn)set.get(0);
                StringBuilder sb = new StringBuilder();

                if (e.schema != null)
                {
                    sb.Append(e.schema + '.');
                }

                if (e.tableName != null)
                {
                    sb.Append(e.tableName + '.');
                }

                throw Error.error(ErrorCode.X_42501,
                                  sb.ToString() + e.getColumnName());
            }
        }

        public override OrderedHashSet getUnkeyedColumns(OrderedHashSet unresolvedSet)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] == null)
                {
                    continue;
                }

                unresolvedSet = nodes[i].getUnkeyedColumns(unresolvedSet);
            }

            if (opType == OpTypes.COLUMN
                    && !rangeVariable.hasKeyedColumnInGroupBy)
            {
                if (unresolvedSet == null)
                {
                    unresolvedSet = new OrderedHashSet();
                }

                unresolvedSet.add(this);
            }

            return unresolvedSet;
        }

        /**
         * collects all range variables in expression tree
         */
        public override void  collectRangeVariables(RangeVariable[] rangeVariables, Set set)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    nodes[i].collectRangeVariables(rangeVariables, set);
                }
            }

            if (rangeVariable != null)
            {
                for (int i = 0; i < rangeVariables.Length; i++)
                {
                    if (rangeVariables[i] == rangeVariable)
                    {
                        set.add(rangeVariables[i]);
                        break;
                    }
                }
            }
        }

        public override Expression replaceAliasInOrderBy(Expression[] columns, int length)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] == null)
                {
                    continue;
                }

                nodes[i] = nodes[i].replaceAliasInOrderBy(columns, length);
            }

            switch (opType)
            {

                case OpTypes.COALESCE:
                case OpTypes.COLUMN:
                    {
                        for (int i = 0; i < length; i++)
                        {
                            QNameManager.SimpleName aliasName = columns[i].alias;
                            String alias = aliasName == null ? null
                                                                     : aliasName.name;

                            if (schema == null && tableName == null
                                    && columnName.Equals(alias))
                            {
                                return columns[i];
                            }
                        }

                        for (int i = 0; i < length; i++)
                        {
                            if (columns[i] is ExpressionColumn)
                            {
                                if (this.Equals(columns[i]))
                                {
                                    return columns[i];
                                }

                                if (tableName == null && schema == null
                                        && columnName
                                            .Equals(((ExpressionColumn)columns[i])
                                                .columnName))
                                {
                                    return columns[i];
                                }
                            }
                        }
                    }
                    break;

            }

            return this;
        }

        public override Expression replaceColumnReferences(RangeVariable range,
                                           Expression[] list)
        {

            if (opType == OpTypes.COLUMN && rangeVariable == range)
            {
                return list[columnIndex];
            }

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] == null)
                {
                    continue;
                }

                nodes[i] = nodes[i].replaceColumnReferences(range, list);
            }

            return this;
        }

        public override int findMatchingRangeVariableIndex(RangeVariable[] rangeVarArray)
        {

            for (int i = 0; i < rangeVarArray.Length; i++)
            {
                RangeVariable rangeVar = rangeVarArray[i];

                if (rangeVar.resolvesTableName(this))
                {
                    return i;
                }
            }

            return -1;
        }

        /**
         * return true if given RangeVariable is used in expression tree
         */
        public override bool hasReference(RangeVariable range)
        {

            if (range == rangeVariable)
            {
                return true;
            }

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    if (nodes[i].hasReference(range))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        public bool equals(Expression other)
        {

            if (other == this)
            {
                return true;
            }

            if (other == null)
            {
                return false;
            }

            if (opType != other.opType)
            {
                return false;
            }

            switch (opType)
            {

                case OpTypes.SIMPLE_COLUMN:
                    return this.columnIndex == other.columnIndex;

                case OpTypes.COALESCE:
                    return nodes == other.nodes;

                case OpTypes.COLUMN:
                    return column == other.getColumn();

                default:
                    return false;
            }
        }
    }
}
